package com.zycfc.zsf.boot.util.text;

import java.security.*;
import java.util.*;
import java.nio.charset.*;
import org.apache.commons.lang3.*;
import java.io.*;
import java.util.zip.*;
import com.google.common.hash.*;

public abstract class HashUtils
{
    private static final ThreadLocal<MessageDigest> MD5_DIGEST;
    private static final ThreadLocal<MessageDigest> SHA_1_DIGEST;
    private static final ThreadLocal<MessageDigest> SHA_256_DIGEST;
    private static SecureRandom RANDOW;
    
    private static ThreadLocal<MessageDigest> createThreadLocalMessageDigest(final String digest) {
        return new ThreadLocal<MessageDigest>() {
            @Override
            protected MessageDigest initialValue() {
                try {
                    return MessageDigest.getInstance(digest);
                }
                catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException("unexpected exception creating MessageDigest instance for [" + digest + "]", e);
                }
            }
        };
    }
    
    public static int hashCode(final Object... objects) {
        return Arrays.hashCode(objects);
    }
    
    public static int hashCode(final Collection<?> list) {
        if (list == null) {
            return 0;
        }
        int hashCode = 1;
        for (final Object obj : list) {
            hashCode = 31 * hashCode + ((obj == null) ? 0 : obj.hashCode());
        }
        return hashCode;
    }
    
    public static byte[] sha1(final byte[] input) {
        return digest(input, get(HashUtils.SHA_1_DIGEST), null, 1);
    }
    
    public static byte[] sha1(final String input) {
        return digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_1_DIGEST), null, 1);
    }
    
    public static byte[] sha1(final byte[] input, final byte[] salt) {
        return digest(input, get(HashUtils.SHA_1_DIGEST), salt, 1);
    }
    
    public static byte[] sha1(final String input, final byte[] salt) {
        return digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_1_DIGEST), salt, 1);
    }
    
    public static byte[] sha1(final byte[] input, final byte[] salt, final int iterations) {
        return digest(input, get(HashUtils.SHA_1_DIGEST), salt, iterations);
    }
    
    public static byte[] sha1(final String input, final byte[] salt, final int iterations) {
        return digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_1_DIGEST), salt, iterations);
    }
    
    public static String sha256(final byte[] input) {
        final byte[] tmp = digest(input, get(HashUtils.SHA_256_DIGEST), null, 1);
        return bytes2Hex(tmp);
    }
    
    public static String sha256(final String input) {
        final byte[] tmp = digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_256_DIGEST), null, 1);
        return bytes2Hex(tmp);
    }
    
    public static String sha256(final byte[] input, final byte[] salt) {
        final byte[] tmp = digest(input, get(HashUtils.SHA_256_DIGEST), salt, 1);
        return bytes2Hex(tmp);
    }
    
    public static String sha256(final String input, final String salt) {
        final byte[] s = (byte[])((null != salt && salt.length() > 0) ? salt.getBytes(StandardCharsets.UTF_8) : null);
        final byte[] tmp = digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_256_DIGEST), s, 1);
        return bytes2Hex(tmp);
    }
    
    public static String sha256(final byte[] input, final byte[] salt, final int iterations) {
        final byte[] tmp = digest(input, get(HashUtils.SHA_256_DIGEST), salt, iterations);
        return bytes2Hex(tmp);
    }
    
    public static String sha256(final String input, final String salt, final int iterations) {
        final byte[] s = (byte[])((null != salt && salt.length() > 0) ? salt.getBytes(StandardCharsets.UTF_8) : null);
        final byte[] tmp = digest(input.getBytes(StandardCharsets.UTF_8), get(HashUtils.SHA_256_DIGEST), s, iterations);
        return bytes2Hex(tmp);
    }
    
    private static MessageDigest get(final ThreadLocal<MessageDigest> messageDigest) {
        final MessageDigest instance = messageDigest.get();
        instance.reset();
        return instance;
    }
    
    private static byte[] digest(final byte[] input, final MessageDigest digest, final byte[] salt, final int iterations) {
        if (salt != null) {
            digest.update(salt);
        }
        byte[] result = digest.digest(input);
        for (int i = 1; i < iterations; ++i) {
            digest.reset();
            result = digest.digest(result);
        }
        return result;
    }
    
    public static String bytes2Hex(final byte[] bts) {
        final StringBuilder des = new StringBuilder();
        String tmp = null;
        for (int i = 0; i < bts.length; ++i) {
            tmp = Integer.toHexString(bts[i] & 0xFF);
            if (tmp.length() == 1) {
                des.append("0");
            }
            des.append(tmp);
        }
        return des.toString();
    }
    
    public static byte[] generateSalt(final int numBytes) {
        Validate.isTrue(numBytes > 0, "numBytes argument must be a positive integer (1 or larger)", (long)numBytes);
        final byte[] bytes = new byte[numBytes];
        HashUtils.RANDOW.nextBytes(bytes);
        return bytes;
    }
    
    public static byte[] sha1File(final InputStream input) throws IOException {
        return digestFile(input, get(HashUtils.SHA_1_DIGEST));
    }
    
    public static byte[] md5File(final InputStream input) throws IOException {
        return digestFile(input, get(HashUtils.MD5_DIGEST));
    }
    
    private static byte[] digestFile(final InputStream input, final MessageDigest messageDigest) throws IOException {
        final int bufferLength = 8192;
        final byte[] buffer = new byte[bufferLength];
        for (int read = input.read(buffer, 0, bufferLength); read > -1; read = input.read(buffer, 0, bufferLength)) {
            messageDigest.update(buffer, 0, read);
        }
        return messageDigest.digest();
    }
    
    public static int crc32AsInt(final String input) {
        return crc32AsInt(input.getBytes(StandardCharsets.UTF_8));
    }
    
    public static int crc32AsInt(final byte[] input) {
        final CRC32 crc32 = new CRC32();
        crc32.update(input);
        return (int)crc32.getValue();
    }
    
    public static long crc32AsLong(final String input) {
        return crc32AsLong(input.getBytes(StandardCharsets.UTF_8));
    }
    
    public static long crc32AsLong(final byte[] input) {
        final CRC32 crc32 = new CRC32();
        crc32.update(input);
        return crc32.getValue();
    }
    
    public static int murmur32AsInt(final byte[] input) {
        return Hashing.murmur3_32().hashBytes(input).asInt();
    }
    
    public static int murmur32AsInt(final String input) {
        return Hashing.murmur3_32().hashString((CharSequence)input, StandardCharsets.UTF_8).asInt();
    }
    
    public static int murmur32AsInt(final byte[] input, final int seed) {
        return Hashing.murmur3_32(seed).hashBytes(input).asInt();
    }
    
    public static int murmur32AsInt(final String input, final int seed) {
        return Hashing.murmur3_32(seed).hashString((CharSequence)input, StandardCharsets.UTF_8).asInt();
    }
    
    static {
        MD5_DIGEST = createThreadLocalMessageDigest("MD5");
        SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1");
        SHA_256_DIGEST = createThreadLocalMessageDigest("SHA-256");
        HashUtils.RANDOW = new SecureRandom();
    }
}
